import functools
import time

import logzero
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import (
    QCheckBox,
    QDoubleSpinBox,
    QGridLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QSpinBox,
    QWidget,
)

from helpers.mengy_bytes_helper import bytes2Int, getChineseStringLength
from mengy_spac800 import DO_NAME, DO_PRT_TYPE_NAME, PTR_SUB_CONFS, getPrtAreaName
from qt_custom_widget.qt_helper import addTooltipInSpinBox, dealGroubBpxDoubleClick
from qt_custom_widget.qt_ScrollMessageBox import ScrollMessageBox

logger = logzero.setup_logger("tabs.5.prt")


class Tab5_Prt_Para_2(QWidget):
    def __init__(self, serRecvDir, sendQueuePack, logger=logger):
        super().__init__()
        self.serRecvDir = serRecvDir
        self.sendQueuePack = sendQueuePack
        self.logger = logger
        self.initUI()
        self.refreshTime = time.time()
        self.logger.info("this is tab5 prt para part 2/2 logger")
        self.setObjectName("tab5")
        layout = QGridLayout(self)
        layout.addWidget(self.prtParaGroupBox_2, 0, 0)  # 保护参数 1

    def initUI(self):
        """初始化显示构件"""
        self.createPrtParaGroupBox_2()

    def createPrtParaSubGroupBox(self, idx, layout):
        """创建保护参数 子布局"""
        self.prtParaOverRangeFlag = False
        self.ptrParaDoData = [0 for i in range(len(DO_PRT_TYPE_NAME))]
        conf = PTR_SUB_CONFS[idx]
        subGB = QGroupBox(conf.name)
        subGB.setCheckable(True)
        subLayout = QGridLayout()
        if conf.doSelectIndex is not None:
            subGB.setToolTip("双击左键反选\n双击右键全选\n双击中键全不选")
            subGB.setToolTipDuration(3000)
            subGB.mouseDoubleClickEvent = functools.partial(
                dealGroubBpxDoubleClick, self.prtDOSelectCheckBoxs[conf.doSelectIndex]
            )
            subLayout.addLayout(self.prtDOSelectGridLayouts[conf.doSelectIndex], 0, 0, 1, 2)
        self.prtWdgts[conf.index - 47] = subGB
        for i, r in enumerate(conf.range):
            subLayout.addWidget(QLabel(conf.itemNames[i]), i + 1, 0)
            if isinstance(r[0], int):
                pWdgt = QSpinBox()
                pWdgt.setSuffix(conf.unitNames[i])
                addTooltipInSpinBox(pWdgt, r)
                pWdgt.setToolTipDuration(3000)
            elif isinstance(r[0], float):
                pWdgt = QDoubleSpinBox()
                pWdgt.setSuffix(conf.unitNames[i])
                pWdgt.setDecimals(2)
                addTooltipInSpinBox(pWdgt, r)
                pWdgt.setToolTipDuration(3000)
            self.prtWdgts[conf.index - 47 + i + 1] = pWdgt
            subLayout.addWidget(pWdgt, i + 1, 1)
        subGB.setLayout(subLayout)
        layout.addWidget(subGB, *conf.groupPlace)

    def createPrtParaGroupBox_2(self):
        """创建保护参数布局"""
        self.prtParaGroupBox_2 = QGroupBox("保护定值 (电机 开入)")
        layout = QGridLayout(self.prtParaGroupBox_2)
        self.prtWdgts = [None for i in range(28)]

        self.prtDOSelectCheckBoxs = []
        self.prtDOSelectGridLayouts = []
        for _ in range(len(DO_PRT_TYPE_NAME)):
            checkBoxs = []
            gLayout = QGridLayout()
            for di in range(len(DO_NAME)):
                qcb = QCheckBox(str(di + 1))
                qcb.setToolTip("1-8代表开出1-8\n9为保护灯\n10为告警灯")
                qcb.setToolTipDuration(3000)
                gLayout.addWidget(qcb, di // 5, di % 5)
                checkBoxs.append(qcb)
            self.prtDOSelectGridLayouts.append(gLayout)
            self.prtDOSelectCheckBoxs.append(checkBoxs)

        for i in range(13):
            self.createPrtParaSubGroupBox(15 + i, layout)

        subGB = QGroupBox("读写控制")
        subLayout = QHBoxLayout()
        self.prtParaGroupBoxReadButton_2 = QPushButton("读取")
        self.prtParaGroupBoxWriteButton_2 = QPushButton("写入")
        self.prtParaGroupBoxReadButton_2.clicked.connect(self.dealPrtParaButton)
        self.prtParaGroupBoxWriteButton_2.clicked.connect(self.dealPrtParaButton)
        subLayout.addWidget(self.prtParaGroupBoxReadButton_2)
        subLayout.addWidget(self.prtParaGroupBoxWriteButton_2)
        subGB.setLayout(subLayout)
        layout.addWidget(subGB, 8, 4, 1, 1)

    def doParaGetPrtData(self):
        """获取开出参数中 保护对应的开出配置"""
        if any((key not in self.serRecvDir.keys() for key in range(0x040A, 0x0426 + 1))):
            return
        for i in range(len(DO_PRT_TYPE_NAME)):
            data = bytes2Int(self.serRecvDir.get(0x040A + i))
            self.ptrParaDoData[i] = data
            # self.logger.info(f"{i+1:>2d} --> 0b{data:010b}")

    def prtParaGroupBoxRead(self):
        """"刷新保护参数显示"""
        raw = tuple(self.serRecvDir.get(i) for i in range(0x082F, 0x084B + 1))
        if any((i is None for i in raw)):
            return
        values = tuple(bytes2Int(i) for i in raw)
        errMsgTextList = []
        for ind, wdgt in enumerate(self.prtWdgts):
            value = values[ind]
            if isinstance(wdgt, QGroupBox):
                if value > 1:
                    wdgt.setStyleSheet("color: red;")
                    prtName = wdgt.title() + "投退定值"
                    prtName = "{0:^{1}}".format(prtName, 24 - getChineseStringLength(prtName))
                    detailText = "{} > 0".format(value)
                    subUnitErrMsgText = "{} 读取值 {:^17} 范围 {} ~ {}".format(prtName, detailText, 0, 1)
                    errMsgTextList.append(subUnitErrMsgText)
                else:
                    wdgt.setStyleSheet("")
                    if value > 0:
                        wdgt.setChecked(True)
                    else:
                        wdgt.setChecked(False)
            elif isinstance(wdgt, QSpinBox) or isinstance(wdgt, QDoubleSpinBox):
                if isinstance(wdgt, QDoubleSpinBox):
                    value = value / (10 ** wdgt.decimals())
                maximum = wdgt.maximum()
                minimum = wdgt.minimum()
                if value > maximum or value < minimum:
                    wdgt.setStyleSheet("color: red;")
                    prtName = getPrtAreaName(ind + 47)
                    prtName = "{0:^{1}}".format(prtName, 24 - getChineseStringLength(prtName))
                    detailText = "{} {}".format(
                        value, "> {}".format(maximum) if value > maximum else "< {}".format(minimum)
                    )
                    subUnitErrMsgText = "{} 读取值 {:^17} 范围 {} ~ {}".format(prtName, detailText, minimum, maximum)
                    errMsgTextList.append(subUnitErrMsgText)
                else:
                    wdgt.setStyleSheet("")
                wdgt.setValue(value)
        if len(errMsgTextList) > 0 and not self.prtParaOverRangeFlag:
            self.prtParaOverRangeFlag = True
            messageBox = ScrollMessageBox(msgList=errMsgTextList, parent=self)
            messageBox.setWindowTitle("保护参数超范围")
            messageBox.exec_()
            self.prtParaOverRangeFlag = False

    def prtParaGroupBoxWrite(self):
        """写入保护参数"""
        data = []
        for wdgt in self.prtWdgts:
            if isinstance(wdgt, QSpinBox):
                data.append(wdgt.value())
            elif isinstance(wdgt, QDoubleSpinBox):
                data.append(int(round(wdgt.value() * (10 ** wdgt.decimals()))))
            elif isinstance(wdgt, QGroupBox):
                wdgt.setStyleSheet("color:#ff0000;")
                if wdgt.isChecked():
                    data.append(1)
                else:
                    data.append(0)
        self.logger.info("in prt data is {}".format(data))
        self.sendQueuePack(funCode=0x10, start=0x082F, data=data)

    def PrtDOSelectGroupBoxRead(self):
        """保护开出读取"""
        self.doParaGetPrtData()
        for idx, value in enumerate(self.ptrParaDoData):
            # self.logger.info(f'index {idx} value {value:010b}')
            for i in range(len(DO_NAME)):
                if value & (1 << i):
                    self.prtDOSelectCheckBoxs[idx][i].setChecked(True)
                else:
                    self.prtDOSelectCheckBoxs[idx][i].setChecked(False)

    def PrtDOSelectGroupBoxWrite(self):
        """保护开出写入"""
        for idx, cs in enumerate(self.prtDOSelectCheckBoxs):
            ptrDoSelect = 0
            for i, c in enumerate(cs):
                if c.isChecked():
                    ptrDoSelect += 1 << i
            self.ptrParaDoData[idx] = ptrDoSelect
        data = self.ptrParaDoData
        self.sendQueuePack(funCode=0x10, start=0x040A, data=data)

    def dealPrtParaButton(self):
        """保护参数按钮处理"""
        sender = self.sender()
        if sender is self.prtParaGroupBoxReadButton_2:
            self.prtParaGroupBoxRead()
            self.PrtDOSelectGroupBoxRead()
        elif sender is self.prtParaGroupBoxWriteButton_2:
            self.prtParaGroupBoxWrite()
            self.PrtDOSelectGroupBoxWrite()
            QTimer.singleShot(1500, self.prtParaGroupBoxRead)
            QTimer.singleShot(1500, self.PrtDOSelectGroupBoxRead)

    def updateBySerialRecvBuff(self, changeAddrList):
        """数据缓存发生变化时需要刷新部分"""
        if any(i in range(0x082F, 0x084B + 1) for i in changeAddrList):
            self.prtParaGroupBoxRead()
            self.refreshTime = time.time()
        if any(i in range(0x040A, 0x0426 + 1) for i in changeAddrList):
            self.PrtDOSelectGroupBoxRead()
            self.refreshTime = time.time()

    def updateByTimer(self, timeout=1):
        if time.time() - self.refreshTime > timeout:
            self.prtParaGroupBoxRead()
            self.PrtDOSelectGroupBoxRead()
            self.refreshTime = time.time()

    def updateUnconditional(self):
        """无条件刷新界面显示部分"""
        pass

    def updateWhole(self):
        """更新全体显示构件"""
        self.prtParaGroupBoxRead()
        self.PrtDOSelectGroupBoxRead()
        self.refreshTime = time.time()
